package server;

import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;

import additions.*;

public class Server {

    int serverPort;
//	int serverLimit;
    ServerSocket server;
//	int onlineUsers;

    //List of all client hadling threads
    Vector handlers;
    ServerInterface window;

    public class ChatHandler implements Runnable {

        protected ClientInfo clientInfo = new ClientInfo();
        protected Socket socket;
        protected ObjectInputStream in;
        protected ObjectOutputStream out;
        //Thread on which to run this handler
        protected Thread listener;

        public ChatHandler(int clientID, Socket socket) {
            clientInfo.clientId = clientID;
            this.socket = socket;
        //onlineUsers = 0;
        }

        protected boolean handShake() {
            try {
                clientInfo.clientName = (String) in.readObject();

                if (window.clientList.size() > 0 && window.clientList.indexOf(clientInfo.clientName) != -1) {
                    try {
                        ConnectionNotice status = new ConnectionNotice();
                        status.status = false;
                        out.writeObject(status);
                        out.flush();
                        socket.close();
                        return false;
                    } catch (IOException w) {
                    }
                }

                try {
                    ConnectionNotice status = new ConnectionNotice();
                    status.status = true;
                    out.writeObject(status);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }

                window.clientList.addElement(clientInfo.clientName);
                out.writeObject(window.clientList);

                UpdateList newClient = new UpdateList();
                newClient.requestType = true;
                newClient.newClient = clientInfo.clientName;
                broadcast(newClient);

//				onlineUsers++;
            } catch (Exception e) {
            }
            return true;
        }

        public synchronized void start() {
            if (listener == null) {
                try {
                    out = new ObjectOutputStream(socket.getOutputStream());
                    in = new ObjectInputStream(socket.getInputStream());

                    ConnectionNotice status = new ConnectionNotice();

                    try {
                        status.status = true;
                        out.writeObject(status);
                    } catch (IOException w) {
                    }

                    if (!handShake()) {
                        return;
                    }

                    //Create a new thread and start listening to the client
                    listener = new Thread(this);
                    listener.start();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }

        public synchronized void stop() {
            if (listener != null) {
                try {
                    listener.interrupt();
                    listener = null;

                    handlers.removeElement(this);
                    window.clientList.removeElement(clientInfo.clientName);
                    UpdateList newClient = new UpdateList();
                    newClient.requestType = false;
                    newClient.newClient = clientInfo.clientName;
                    broadcast(newClient);

                    out.close();
                    socket.close();
                    window.taMessages.append("Connection to client " + clientInfo.clientName + " closed...\n");
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(window, "Error closing connection to client", "Termination Error", JOptionPane.ERROR_MESSAGE);
                }
            }
        }

        public void run() {
            try {
                handlers.addElement(this);

                //Listen to input stream for messages from this client
                while (true) {
                    try {
                        Object clientMsg = in.readObject();

                        if (clientMsg instanceof Message) {
                            //If public message then broadcast
                            if (((Message) clientMsg).isPublic == true) {
                                broadcast(clientMsg);
                            } else //private message
                            {
                                ((ChatHandler) (handlers.elementAt(((Message) clientMsg).recieverId))).out.writeObject(clientMsg);
                            }
                        } else if (clientMsg instanceof ChatRequest) {
                            ((ChatHandler) (handlers.elementAt(((ChatRequest) clientMsg).recieverId))).out.writeObject(clientMsg);
                        } else if (clientMsg instanceof LogOut) {
//							onlineUsers--;
                            break;
                        } else {
                            System.out.println(clientMsg);
                        }
                    } catch (ClassNotFoundException ex) {
                        JOptionPane.showMessageDialog(window, "Class of a serialized object cannot be found.", "Termination Error", JOptionPane.ERROR_MESSAGE);
                        break;
                    } catch (InvalidClassException ex) {
                        JOptionPane.showMessageDialog(window, "Something is wrong with a class used by serialization.", "Termination Error", JOptionPane.ERROR_MESSAGE);
                        break;
                    } catch (StreamCorruptedException ex) {
                        JOptionPane.showMessageDialog(window, "Control information in the stream is inconsistent.", "Termination Error", JOptionPane.ERROR_MESSAGE);
                        break;
                    } catch (OptionalDataException ex) {
                        JOptionPane.showMessageDialog(window, "Primitive data was found in the stream instead of objects.", "Termination Error", JOptionPane.ERROR_MESSAGE);
                        break;
                    } catch (IOException ex) {
                        JOptionPane.showMessageDialog(null, "Any of the usual Input/Output related exceptions.", "Termination Error", JOptionPane.ERROR_MESSAGE);
                        break;
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                stop();
            }
        }

        //Broadcasts a message to all clients
        protected void broadcast(Object clientMsg) {
            synchronized (handlers) {
                Enumeration enum1 = handlers.elements();

                //Loop through all the clients
                while (enum1.hasMoreElements()) {
                    ChatHandler handler = (ChatHandler) enum1.nextElement();
                    try {
                        handler.out.writeObject(clientMsg);
                        out.flush();
                    } catch (IOException ex) {
                        handler.stop();
                    }
                }
            }
        }
    }


    //Listen to connection requests from clients
    protected void listenForClients(ServerSocket server) {
        window.taMessages.append("Server running on port: " + serverPort + "\n\n");

        int clientID = 0;

        while (true) {
            try {
                Socket client = server.accept();
                ChatHandler handler = new ChatHandler(++clientID, client);
                window.taMessages.append("Connection accepted: " + clientID + "\n");
                handler.start();
            } catch (IOException ex) {
                JOptionPane.showMessageDialog(window, "Cannot Setup Connection", "Client Connection Error", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    protected void shutDown() {
        Enumeration enum1 = handlers.elements();
        ShutDown shutdown = new ShutDown();

        synchronized (handlers) {
            //Close connection to all clients.Stop all handlers
            while (enum1.hasMoreElements()) {
                try {
                    ((ChatHandler) (handlers.firstElement())).out.writeObject(shutdown);
                } catch (Exception e) {
                }

                ((ChatHandler) (handlers.firstElement())).stop();
            }
        }
        System.exit(0);
    }

    protected synchronized void loadSettings() {
        try {
            FileReader file = new FileReader("src/server.txt");
            BufferedReader in = new BufferedReader(file);

            String s = in.readLine();
            serverPort = Integer.parseInt(s);
            file.close();
        } catch (FileNotFoundException ex) {
            JOptionPane.showMessageDialog(null, "Configuration File Not Found, Using Defaults", "Configuration File Missing", JOptionPane.ERROR_MESSAGE);
            serverPort = 1313;
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(null, "Error Reading Configuration File, Using Defaults", "Configuration Error", JOptionPane.ERROR_MESSAGE);
            serverPort = 1313;
        }
    }

    Server() {
        loadSettings();
        //		onlineUsers=0;
        try {
            server = new ServerSocket(serverPort);
            handlers = new Vector();
            window = new ServerInterface(this);
            listenForClients(server);
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(null, "Cannot Start Server", "ServerSocket Error", JOptionPane.ERROR_MESSAGE);
            System.exit(0);
        } finally {
            try {
                if (server != null) {
                    server.close();
                }
            } catch (IOException e) {
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Server();
    }
}